package com.mrzhou.swagger.config;

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import com.mrzhou.core.tool.constants.TokenConstant;
import com.mrzhou.swagger.util.SwaggerUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

@Slf4j
@Configuration
@EnableSwagger2WebMvc
public class Swagger2Configuration {

    private static final String DEFAULT_EXCLUDE_PATH = "/error";
    private static final String BASE_PATH = "/**";
    /**  OAuth2.0协议需要使用的标椎请求头 */
    private static final String AUTH_HEADER_NAME =  "Authorization";
    /**  目中jwtToken的header头 */
    private static final String TOKEN_HEADER_NAME = TokenConstant.TOKEN_HEADER_NAME;

    /**
     * 在不写入任何参数表示: 如Bean容器类不存在同类型的Bean则注入, 否则不注入; @ConditionalOnBean反之
     * @param properties
     * @return
     */
    @Bean
    @ConditionalOnMissingBean
    public Docket createRestApi(Swagger2Properties properties) {
        log.info("Swagger2的基本配置: {}", properties);
        if(properties.getBasePackage().size() == 0) {
            properties.getBasePackage().add(BASE_PATH);
        }
        if(properties.getExcludPath().size() == 0) {
            properties.getExcludPath().add(DEFAULT_EXCLUDE_PATH);
        }

        return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo(properties))
                .groupName(properties.getGroupName())
                // 选择那些路径和api会生成document
                .select()
                // swagger扫描那些类路径下的接口
                .apis(SwaggerUtil.basePackages(properties.getBasePackage()))
                .paths((path) -> properties.getExcludPath().stream().anyMatch(var -> !var.endsWith(path)))
                .build()
                .securitySchemes(securitySchemes())
                .securityContexts(securityContexts());
    }

    /**
     * 通过Swagger2的securitySchemes配置全局参数：如下列代码所示，
     * 	securitySchemes的ApiKey中增加一个名为“token-name”，
     * 	type为“header”的参数。
     * @return
     */
    private List<ApiKey> securitySchemes() {
        List<ApiKey> apiKeys = new ArrayList<>();
        apiKeys.add(new ApiKey(AUTH_HEADER_NAME, AUTH_HEADER_NAME, "header"));
        apiKeys.add(new ApiKey(TOKEN_HEADER_NAME, TOKEN_HEADER_NAME, "header"));
        return apiKeys;
    }

    /**
     * 在Swagger2的securityContexts中通过正则表达式，
     * 	设置需要使用参数的接口(或者说，是去除掉不需要使用参数的接口)，如下列代码所示，
     * 		通过PathSelectors.regex("/.*?")，
     * 		所有包含"auth"的接口不需要使用securitySchemes。
     * 		即不需要使用上文中设置的名为“Authorization”，type为“header”的参数。
     * @return
     */
    private List<SecurityContext> securityContexts() {
        List<SecurityContext> scs = new ArrayList<>();
        scs.add(SecurityContext.builder()
                        .securityReferences(defaultAuth())
                        .forPaths(PathSelectors.regex("/.*?"))
                        .build());
        return scs;
    }

    List<SecurityReference> defaultAuth() {
        AuthorizationScope authorizationScope = new AuthorizationScope(AUTH_HEADER_NAME, "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[2];
        authorizationScopes[0] = authorizationScope;
        authorizationScopes[1] = new AuthorizationScope(TOKEN_HEADER_NAME, "accessEverything");
        List<SecurityReference> securityReferences=new ArrayList<>();
        // 此处注意: reference参数 值需和AuthorizationScope与ApiKey的参数一致
        securityReferences.add(new SecurityReference(AUTH_HEADER_NAME, authorizationScopes));
        securityReferences.add(new SecurityReference(TOKEN_HEADER_NAME, authorizationScopes));
        return securityReferences;
    }

    private ApiInfo apiInfo(Swagger2Properties properties) {
        return new ApiInfoBuilder()
                .title(properties.getTitle())
                .license(properties.getLicense())
                .description(properties.getDescription())
                .version(properties.getVersion())
                .build();
    }


}
